/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// r_main.c

#include "quakedef.h"

cvar_t *gl_fogdensity = NULL;
cvar_t *gl_fogred = NULL;
cvar_t *gl_foggreen = NULL;
cvar_t *gl_fogblue = NULL;
cvar_t *gl_fogtime = NULL;
cvar_t *r_skyfog = NULL;

typedef struct fogdef_s
{
	float prevval;
	float currval;
	float lerptime;
	float lerpdone;
} fogdef_t;

fogdef_t fog_red = {0.3f, 0.3f, 0.1f, 0.0f};
fogdef_t fog_green = {0.3f, 0.3f, 0.1f, 0.0f};
fogdef_t fog_blue = {0.3f, 0.3f, 0.1f, 0.0f};
fogdef_t fog_density = {0.0f, 0.0f, 0.1f, 0.0f};
fogdef_t fog_sky = {0.5f, 0.5f, 0.1, 0.0f};


float Fog_LerpFog (fogdef_t *def)
{
	if (key_dest != key_game)
	{
		def->lerpdone = cl.time;
		return def->currval;
	}
	else if (cl.time < def->lerpdone)
	{
		float f = (def->lerpdone - cl.time) / def->lerptime;
		return f * def->prevval + (1.0f - f) * def->currval;
	}
	else return def->currval;
}


float Fog_GetLerp (fogdef_t *def, float val)
{
	if (val > 0 && (def->currval > 0 || def->prevval > 0))
	{
		if (def->currval > def->prevval)
			return 1.0f - (def->currval - val) / (def->currval - def->prevval);
		else if (def->prevval > def->currval)
			return 1.0f - (def->prevval - val) / (def->prevval - def->currval);
		else return 1.0f;
	}

	return 0.0f;
}


void Fog_UpdateValue (fogdef_t *def, cvar_t *var)
{
	if (var)
	{
		def->prevval = def->currval;
		def->currval = var->value;
		def->lerptime = gl_fogtime->value;
		def->lerpdone = cl.time + gl_fogtime->value;
	}
}


void Fog_Update (float density, float red, float green, float blue, float time)
{
	// time is set first so that everything can have proper lerptimes
	Cvar_SetValue (gl_fogtime, time);

	// now set everything else
	Cvar_SetValue (gl_fogdensity, density);
	Cvar_SetValue (gl_fogred, red);
	Cvar_SetValue (gl_foggreen, green);
	Cvar_SetValue (gl_fogblue, blue);
}


void Fog_Resync (void)
{
	Fog_UpdateValue (&fog_density, gl_fogdensity);
	Fog_UpdateValue (&fog_red, gl_fogred);
	Fog_UpdateValue (&fog_green, gl_foggreen);
	Fog_UpdateValue (&fog_blue, gl_fogblue);
}


void Fog_ParseServerMessage (void)
{
	float density = MSG_ReadByte () / 255.0f;
	float red = MSG_ReadByte () / 255.0f;
	float green = MSG_ReadByte () / 255.0f;
	float blue = MSG_ReadByte () / 255.0f;
	float time = MSG_ReadShort () / 100.0f;

	Fog_Update (density, red, green, blue, time);
}


void Fog_SetupFrame (void)
{
	/*
	float density = Fog_LerpFog (&fog_density);

	// untransformed eye position goes in register 28 for particles and sprites
	d3d_State->VSSetConstant3fv (VS_EYEPOSITIONWORLD, ref.ViewOrigin);

	// fog variables always go in these registers
	d3d_State->VSSetConstantf (VS_FOGCOLOUR, Fog_LerpFog (&fog_red), Fog_LerpFog (&fog_green), Fog_LerpFog (&fog_blue));
	d3d_State->VSSetConstantf (VS_FOGDENSITY, density * (1.0f / 64.0f));

	d3d_State->PSSetConstantf (PS_FOGCOLOUR, Fog_LerpFog (&fog_red), Fog_LerpFog (&fog_green), Fog_LerpFog (&fog_blue));
	d3d_State->PSSetConstantf (PS_SKYFOGDENSITY, Fog_GetLerp (&fog_density, density) * Fog_LerpFog (&fog_sky));
	*/
}


void Fog_SetEyePosition (QMATRIX &m)
{
	/*
	float eyeposition[3];

	// transformed eye position goes in register 27 for everything else
	m.InverseTransform (eyeposition, ref.ViewOrigin);
	d3d_State->VSSetConstant3fv (VS_EYEPOSITIONLOCAL, eyeposition);
	*/
}


void Fog_FogCommand_f (void)
{
	switch (Cmd_Argc ())
	{
	default:
	case 1:
		Con_Printf (PRINT_SAFE, "usage:\n");
		Con_Printf (PRINT_SAFE, "   fog <density>\n");
		Con_Printf (PRINT_SAFE, "   fog <red> <green> <blue>\n");
		Con_Printf (PRINT_SAFE, "   fog <density> <red> <green> <blue>\n");
		Con_Printf (PRINT_SAFE, "current values:\n");
		Con_Printf (PRINT_SAFE, "   \"density\" is \"%f\"\n", gl_fogdensity->value);
		Con_Printf (PRINT_SAFE, "   \"red\" is \"%f\"\n", gl_fogred->value);
		Con_Printf (PRINT_SAFE, "   \"green\" is \"%f\"\n", gl_foggreen->value);
		Con_Printf (PRINT_SAFE, "   \"blue\" is \"%f\"\n", gl_fogblue->value);
		break;

	case 2:
		Fog_Update (
			atof (Cmd_Argv (1)),
			gl_fogred->value,
			gl_foggreen->value,
			gl_fogblue->value,
			gl_fogtime->value
		); break;

	case 3:
		Fog_Update (
			atof (Cmd_Argv (1)),
			gl_fogred->value,
			gl_foggreen->value,
			gl_fogblue->value,
			atof (Cmd_Argv (2))
		); break;

	case 4:
		Fog_Update (
			gl_fogdensity->value,
			atof (Cmd_Argv (1)),
			atof (Cmd_Argv (2)),
			atof (Cmd_Argv (3)),
			gl_fogtime->value
		); break;

	case 5:
		Fog_Update (
			atof (Cmd_Argv (1)),
			atof (Cmd_Argv (2)),
			atof (Cmd_Argv (3)),
			atof (Cmd_Argv (4)),
			gl_fogtime->value
		); break;

	case 6:
		Fog_Update (
			atof (Cmd_Argv (1)),
			atof (Cmd_Argv (2)),
			atof (Cmd_Argv (3)),
			atof (Cmd_Argv (4)),
			atof (Cmd_Argv (5))
		); break;
	}
}


void Fog_UpdateDensity (void) {Fog_UpdateValue (&fog_density, gl_fogdensity);}
void Fog_UpdateRed (void) {Fog_UpdateValue (&fog_red, gl_fogred);}
void Fog_UpdateGreen (void) {Fog_UpdateValue (&fog_green, gl_foggreen);}
void Fog_UpdateBlue (void) {Fog_UpdateValue (&fog_blue, gl_fogblue);}
void Fog_UpdateSky (void) {Fog_UpdateValue (&fog_sky, r_skyfog);}


void Fog_Init (void)
{
	// time should be registered first as the others depend on it
	gl_fogtime = Cvar_Get ("gl_fogtime", "0.1");

	// and now register the others
	gl_fogdensity = Cvar_Get ("gl_fogdensity", "0", 0, Fog_UpdateDensity);
	gl_fogred = Cvar_Get ("gl_fogred", "0.3", 0, Fog_UpdateRed);
	gl_foggreen = Cvar_Get ("gl_foggreen", "0.3", 0, Fog_UpdateGreen);
	gl_fogblue = Cvar_Get ("gl_fogblue", "0.3", 0, Fog_UpdateBlue);
	r_skyfog = Cvar_Get ("r_skyfog", "0.5", 0, Fog_UpdateSky);

	// make everything consistent
	Fog_UpdateValue (&fog_density, gl_fogdensity);
	Fog_UpdateValue (&fog_red, gl_fogred);
	Fog_UpdateValue (&fog_green, gl_foggreen);
	Fog_UpdateValue (&fog_blue, gl_fogblue);
	Fog_UpdateValue (&fog_sky, r_skyfog);

	Cmd_AddCommand ("fog", Fog_FogCommand_f);
}


void Fog_NewMap (void)
{
	char *key = (char *) qScratch->FastAlloc (1025);
	char *value = (char *) qScratch->FastAlloc (1025);
	char *data;

	// initially no fog; bring cvars back to their defaults
	Fog_Update (0.0f, 0.3f, 0.3f, 0.3f, gl_fogtime->value);

	// running the update twice to clear both the current and previous values
	Fog_Resync ();

	// and start parsing the data
	data = COM_Parse (cl.worldmodel->entities);

	if (!data) return; // error
	if (com_token[0] != '{') return; // error

	while (1)
	{
		if ((data = COM_Parse (data)) == NULL) return; // error
		if (com_token[0] == '}') break; // end of worldspawn

		if (com_token[0] == '_')
			strcpy (key, com_token + 1);
		else strcpy (key, com_token);

		while (key[strlen (key) - 1] == ' ') // remove trailing spaces
			key[strlen (key) - 1] = 0;

		if ((data = COM_Parse (data)) == NULL)
			return; // error

		strcpy (value, com_token);

		if (!strcmp ("fog", key))
		{
			float fogparms[4];

			// scan into values
			if (sscanf (value, "%f %f %f %f", &fogparms[0], &fogparms[1], &fogparms[2], &fogparms[3]) == 4)
			{
				// copy to cvars to keep everything consistent
				Fog_Update (fogparms[0], fogparms[1], fogparms[2], fogparms[3], gl_fogtime->value);

				// once is all we need
				break;
			}
		}
	}

	// run another resync to bring the updated values consistent for both prev and curr
	Fog_Resync ();
}

